Profiler.render   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 3
c 0
b 0
f 0
rs 10
cc 1
1
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
3
const browser = require('@sentry/browser');
4
const core = require('@sentry/core');
5
const React = require('react');
6
const constants = require('./constants.js');
7
const hoistNonReactStatics = require('./hoist-non-react-statics.js');
8
9
const UNKNOWN_COMPONENT = 'unknown';
10
11
/**
12
 * The Profiler component leverages Sentry's Tracing integration to generate
13
 * spans based on component lifecycles.
14
 */
15
class Profiler extends React.Component {
16
  /**
17
   * The span of the mount activity
18
   * Made protected for the React Native SDK to access
19
   */
20
21
  /**
22
   * The span that represents the duration of time between shouldComponentUpdate and componentDidUpdate
23
   */
24
25
   constructor(props) {
26
    super(props);
27
    const { name, disabled = false } = this.props;
28
29
    if (disabled) {
30
      return;
31
    }
32
33
    this._mountSpan = browser.startInactiveSpan({
34
      name: `<${name}>`,
35
      onlyIfParent: true,
36
      op: constants.REACT_MOUNT_OP,
37
      attributes: {
38
        [core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.react.profiler',
39
        'ui.component_name': name,
40
      },
41
    });
42
  }
43
44
  // If a component mounted, we can finish the mount activity.
45
   componentDidMount() {
46
    if (this._mountSpan) {
47
      this._mountSpan.end();
48
    }
49
  }
50
51
   shouldComponentUpdate({ updateProps, includeUpdates = true }) {
52
    // Only generate an update span if includeUpdates is true, if there is a valid mountSpan,
53
    // and if the updateProps have changed. It is ok to not do a deep equality check here as it is expensive.
54
    // We are just trying to give baseline clues for further investigation.
55
    if (includeUpdates && this._mountSpan && updateProps !== this.props.updateProps) {
56
      // See what props have changed between the previous props, and the current props. This is
57
      // set as data on the span. We just store the prop keys as the values could be potentially very large.
58
      const changedProps = Object.keys(updateProps).filter(k => updateProps[k] !== this.props.updateProps[k]);
59
      if (changedProps.length > 0) {
60
        const now = core.timestampInSeconds();
61
        this._updateSpan = core.withActiveSpan(this._mountSpan, () => {
62
          return browser.startInactiveSpan({
63
            name: `<${this.props.name}>`,
64
            onlyIfParent: true,
65
            op: constants.REACT_UPDATE_OP,
66
            startTime: now,
67
            attributes: {
68
              [core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.react.profiler',
69
              'ui.component_name': this.props.name,
70
              'ui.react.changed_props': changedProps,
71
            },
72
          });
73
        });
74
      }
75
    }
76
77
    return true;
78
  }
79
80
   componentDidUpdate() {
81
    if (this._updateSpan) {
82
      this._updateSpan.end();
83
      this._updateSpan = undefined;
84
    }
85
  }
86
87
  // If a component is unmounted, we can say it is no longer on the screen.
88
  // This means we can finish the span representing the component render.
89
   componentWillUnmount() {
90
    const endTimestamp = core.timestampInSeconds();
91
    const { name, includeRender = true } = this.props;
92
93
    if (this._mountSpan && includeRender) {
94
      const startTime = core.spanToJSON(this._mountSpan).timestamp;
95
      core.withActiveSpan(this._mountSpan, () => {
96
        const renderSpan = browser.startInactiveSpan({
97
          onlyIfParent: true,
98
          name: `<${name}>`,
99
          op: constants.REACT_RENDER_OP,
100
          startTime,
101
          attributes: {
102
            [core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.react.profiler',
103
            'ui.component_name': name,
104
          },
105
        });
106
        if (renderSpan) {
107
          // Have to cast to Span because the type of _mountSpan is Span | undefined
108
          // and not getting narrowed properly
109
          renderSpan.end(endTimestamp);
110
        }
111
      });
112
    }
113
  }
114
115
   render() {
116
    return this.props.children;
117
  }
118
}
119
120
// React.Component default props are defined as static property on the class
121
Object.assign(Profiler, {
122
  defaultProps: {
123
    disabled: false,
124
    includeRender: true,
125
    includeUpdates: true,
126
  },
127
});
128
129
/**
130
 * withProfiler is a higher order component that wraps a
131
 * component in a {@link Profiler} component. It is recommended that
132
 * the higher order component be used over the regular {@link Profiler} component.
133
 *
134
 * @param WrappedComponent component that is wrapped by Profiler
135
 * @param options the {@link ProfilerProps} you can pass into the Profiler
136
 */
137
// eslint-disable-next-line @typescript-eslint/no-explicit-any
138
function withProfiler(
139
  WrappedComponent,
140
  // We do not want to have `updateProps` given in options, it is instead filled through the HOC.
141
  options,
142
) {
143
  const componentDisplayName =
144
    options?.name || WrappedComponent.displayName || WrappedComponent.name || UNKNOWN_COMPONENT;
145
146
  const Wrapped = (props) => (
147
    React.createElement(Profiler, { ...options, name: componentDisplayName, updateProps: props,}
148
      , React.createElement(WrappedComponent, { ...props,} )
149
    )
150
  );
151
152
  Wrapped.displayName = `profiler(${componentDisplayName})`;
153
154
  // Copy over static methods from Wrapped component to Profiler HOC
155
  // See: https://reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over
156
  hoistNonReactStatics.hoistNonReactStatics(Wrapped, WrappedComponent);
157
  return Wrapped;
158
}
159
160
/**
161
 *
162
 * `useProfiler` is a React hook that profiles a React component.
163
 *
164
 * Requires React 16.8 or above.
165
 * @param name displayName of component being profiled
166
 */
167
function useProfiler(
168
  name,
169
  options = {
170
    disabled: false,
171
    hasRenderSpan: true,
172
  },
173
) {
174
  const [mountSpan] = React.useState(() => {
175
    if (options?.disabled) {
176
      return undefined;
177
    }
178
179
    return browser.startInactiveSpan({
180
      name: `<${name}>`,
181
      onlyIfParent: true,
182
      op: constants.REACT_MOUNT_OP,
183
      attributes: {
184
        [core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.react.profiler',
185
        'ui.component_name': name,
186
      },
187
    });
188
  });
189
190
  React.useEffect(() => {
191
    if (mountSpan) {
192
      mountSpan.end();
193
    }
194
195
    return () => {
196
      if (mountSpan && options.hasRenderSpan) {
197
        const startTime = core.spanToJSON(mountSpan).timestamp;
198
        const endTimestamp = core.timestampInSeconds();
199
200
        const renderSpan = browser.startInactiveSpan({
201
          name: `<${name}>`,
202
          onlyIfParent: true,
203
          op: constants.REACT_RENDER_OP,
204
          startTime,
205
          attributes: {
206
            [core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.react.profiler',
207
            'ui.component_name': name,
208
          },
209
        });
210
        if (renderSpan) {
211
          // Have to cast to Span because the type of _mountSpan is Span | undefined
212
          // and not getting narrowed properly
213
          renderSpan.end(endTimestamp);
214
        }
215
      }
216
    };
217
    // We only want this to run once.
218
    // eslint-disable-next-line react-hooks/exhaustive-deps
219
  }, []);
220
}
221
222
exports.Profiler = Profiler;
223
exports.UNKNOWN_COMPONENT = UNKNOWN_COMPONENT;
224
exports.useProfiler = useProfiler;
225
exports.withProfiler = withProfiler;
226
//# sourceMappingURL=profiler.js.map
227